using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using HappySprite;
using HappySprite.Simulation;

namespace HappyInvaders
{
    class Model
    {
        int level;
        int lives;
        int score;

        int bombCount;
        int bulletCount;
        int saucerCount;
        int zapCount;

        int invadersLeft;
        int invaderIndex;
        int invaderDirection;
	    bool invaderDownNext;
		bool invaderDown;

        int maxBombs;
        int bombChance;

        bool won;
        bool lost;

        PlayerInput playerInput;
        PlayerEntity player;
        ExplosionEntity explosion;
        World world;
        InvaderEntity[] invaders;

        List<Entity> entities = new List<Entity>();

        public event GenericEventHandler<Entity> EntityCreated;

        public event GenericEventHandler LostGame;
        public event GenericEventHandler WonLevel;

        public Model()
        {
            world = new World();
        }

        public bool GameOver
        {
            get { return won || lost || level <= 0; }
        }

        public int Level
        {
            get { return level; }
        }

        public int Lives
        {
            get { return lives; }
            set { lives = value; }
        }

        public int Score
        {
            get { return score; }
            set { score = value; }
        }

        public void Clear()
        {
            List<Entity> workingEntities = new List<Entity>(entities);

            foreach (Entity entity in workingEntities)
                entity.Remove();

            // Cancel any pending events
            Global.EventScheduler.Cancel(SpawnSaucer);
            Global.EventScheduler.Cancel(UpdateInvaders);
        }

        public void SetPlayerInput(PlayerInput playerInput)
        {
            this.playerInput = playerInput;
        }

        public void StartLevel(int level)
        {
            this.level = level;
            invaders = new InvaderEntity[55];
            invadersLeft = 55;
            invaderIndex = -1;
            invaderDirection = 1;
            invaderDown = false;
            invaderDownNext = false;

            maxBombs = Math.Min(20, level + 11);
            bombChance = Math.Max(80, 96 - level);

            won = false;
            lost = false;

            // Create the player
            CreatePlayer();

            // Create the shelters (4 shelters)
            float margin = Constants.PlayerWidth * 0.9f + Constants.ShelterWidth / 2f;
            float shelterSpace = Constants.PlayerMaxX - Constants.PlayerMinX - 2f * margin;

            for (int i = 0; i < 4; i++)
            {
                float x = Constants.PlayerMinX + margin + i * shelterSpace / 3f;
                ShelterEntity shelter = Global.RecycleBin.Create<ShelterEntity>();
                shelter.Position = new Vector2(x, Constants.ShelterY);

                AddToWorld(shelter);
            }

            // Create the invaders (5 rows, 11 columns)
            int index = 0;
            for (int row = 0; row < 5; row++)
            {
                for (int col = 0; col < 11; col++)
                {
                    float x = Constants.PlayerMinX + Constants.PlayerWidth / 2f + col * 16f;
                    float y = Constants.PlayerY + Constants.PlayerHeight + 2f + Constants.InvaderHeight + (6 - level) * 14f + row * 14f;
                    Vector2 position = new Vector2(x, y);

                    InvaderEntity invader = Global.RecycleBin.Create<InvaderEntity>();
                    invaders[index] = invader;

                    invader.Position = position;
                    invader.Row = row;
                    invader.Index = index;
                    AddToWorld(invader);

                    index++;
                }
            }

            // Kick off Invader cycle
            ScheduleInvaderCycle();

            // Kick off saucer spawn
            ScheduleSpawnSaucer();
        }

        private void CreatePlayer()
        {
            PlayerEntity player = Global.RecycleBin.Create<PlayerEntity>();
            player.Position = new Vector2(Constants.PlayerStartX, Constants.PlayerY);
            AddToWorld(player);
        }

        public void Update(GameTime gameTime)
        {
            // If we are in won or lost state, exit
            if (GameOver)
                return;

            // Move player and check for bullet fire
            if (Player != null)
                Player.HandlePlayerInput(playerInput);

            // Update the World
            world.Update(gameTime);

            // Update the entities
            foreach (Entity entity in entities)
                entity.Update();

            // State transition checks
            UpdateState();
        }

        // These methods and properties called by entities
        internal int BulletCount
        {
            get { return bulletCount; }
            set { bulletCount = value; }
        }

        internal int BombCount
        {
            get { return bombCount; }
            set { bombCount = value; }
        }

        internal ExplosionEntity Explosion
        {
            get { return explosion; }
            set { explosion = value; }
        }

        internal PlayerEntity Player
        {
            get { return player; }
            set { player = value; }
        }

        internal int SaucerCount
        {
            get { return saucerCount; }
            set { saucerCount = value; }
        }

        internal int ZapCount
        {
            get { return zapCount; }
            set { zapCount = value; }
        }

        internal void CreateBomb(Vector2 position)
        {
            Entity entity = Global.RecycleBin.Create<BombEntity>();
            entity.Position = position;
            AddToWorld(entity);
        }

        internal void CreateBullet(Vector2 position)
        {
            Entity entity = Global.RecycleBin.Create<BulletEntity>();
            entity.Position = position;
            AddToWorld(entity);
        }

        internal void CreateExplosion(Vector2 position)
        {
            Entity entity = Global.RecycleBin.Create<ExplosionEntity>();
            entity.Position = position;
            AddToWorld(entity);
        }

        internal void CreateZap(Vector2 position)
        {
            Entity entity = Global.RecycleBin.Create<ZapEntity>();
            entity.Position = position;
            AddToWorld(entity);
        }

        internal void InvaderRemoved(int index)
        {
            invadersLeft--;
            invaders[index] = null;
        }

        internal void FireEntityCreatedEvent(Entity entity)
        {
            if (EntityCreated != null)
                EntityCreated(entity);
        }

        internal void RemoveEntity(Entity entity)
        {
            entities.Remove(entity);
        }

        internal void ScheduleSpawnSaucer()
        {
            Global.EventScheduler.Schedule(Constants.SaucerRespawn, SpawnSaucer);
        }

        void UpdateState()
        {
            lost = player == null && explosion == null;
            won = invadersLeft <= 0 && ZapCount <= 0 && BombCount <= 0 && SaucerCount <= 0;

            if (lost)
            {
                lives--;

                if (lives > 0)
                {
                    CreatePlayer();
                    lost = false;
                }
                else
                {
                    Clear();

                    // Fire lost event
                    if (LostGame != null)
                        LostGame();
                }
            }

            if (won)
            {
                Clear();

                // Fire won event
                if (WonLevel != null)
                    WonLevel();
            }
        }

        void SpawnSaucer()
        {
            SaucerEntity saucer = Global.RecycleBin.Create<SaucerEntity>();

            if (Global.Random.NextDouble() < 0.5f)
                saucer.StartLeft();
            else
                saucer.StartRight();

            AddToWorld(saucer);
        }

        void AddToWorld(Entity entity)
        {
            world.Add(entity.Item);
            entities.Add(entity);
            entity.Owner = this;
            entity.OnAdd();

            // Fire event
            FireEntityCreatedEvent(entity);
        }

        void ScheduleInvaderCycle()
        {
            Global.EventScheduler.Schedule(Constants.InvaderCycle, UpdateInvaders);
        }

        void UpdateInvaders()
        {
            // Setup next cycle
            ScheduleInvaderCycle();

            // Conditions which pause invader logic
            if (invadersLeft <= 0 || ZapCount > 0 || explosion != null)
                return;

            // Get next living invader
            InvaderEntity invader = NextInvader();

            // Update invader position
            Vector2 position = invader.Position;

            if (invaderDown)
            {
                position.Y -= 7f;
            }
            else
            {
                position.X += 2f * invaderDirection;
                if ((position.X <= Constants.PlayerMinX && invaderDirection < 0) ||
                    (position.X >= Constants.PlayerMaxX && invaderDirection > 0))
                    invaderDownNext = true;
            }

            invader.Position = position;

            // Check for bomb
            if (BombCount < maxBombs && Global.Random.Next(100) > bombChance)
            {
                bool obstruted = false;
                for (int i = invaderIndex - 11; i >= 0; i -= 11)
                {
                    if (invaders[i] != null)
                    {
                        obstruted = true;
                        break;
                    }
                }

                if (!obstruted)
                {
                    // Set the bomb position right below the invader
                    Vector2 bombPosition = invader.Position;
                    bombPosition.Y -= Constants.InvaderHeight;
                    CreateBomb(bombPosition);
                }
            }
        }

        InvaderEntity NextInvader()
        {
            InvaderEntity invader = null;

            while (invader == null)
            {
                if (++invaderIndex >= 55)
                {
                    invaderIndex = 0;
                    if (invaderDown)
                    {
                        invaderDown = false;
                        invaderDirection *= -1;
                    }

                    if (invaderDownNext)
                    {
                        invaderDown = true;
                        invaderDownNext = false;
                    }
                }

                invader = invaders[invaderIndex];
            }

            return invader;
        }
    }
}
